Skip to content

fix(hetzner): register env-scoped SSH keys once across realization scopes#154

Merged
pedromvgomes merged 1 commit into
mainfrom
fix/hetzner-ssh-keys-cross-scope
Jun 28, 2026
Merged

fix(hetzner): register env-scoped SSH keys once across realization scopes#154
pedromvgomes merged 1 commit into
mainfrom
fix/hetzner-ssh-keys-cross-scope

Conversation

@pedromvgomes

Copy link
Copy Markdown
Contributor

Problem

The first real cross-scope deployment (wardnet-infrastructure PR #31) failed at inforge preview with:

error: Duplicate resource URN 'urn:pulumi:prd::wardnet-infrastructure::hcloud:index/sshKey:SshKey::wardnet-prd-key-user'

program.Run builds one HetznerCompute per realization scope (the region-less global slice plus each region), each with its own SSH-key dedup cache. Hetzner SSH keys are account-global and named with no region slug (wardnet-<env>-key-{user,deploy}, via naming.GlobalResource), so every scope registered the same URN. The dedup never spanned scopes.

This is the sibling of the already-documented registry-provider-names-are-region-scoped bug: providers legitimately need a unique URN per scope, whereas these env-scoped keys must be created exactly once total.

Fix

  • Share one SSHKeyCache across every HetznerCompute of a run, threaded program.RunBuildRegistryNewCompute, so the keys register exactly once.
  • Pin the keys to a dedicated, fixed-name provider (hcloud-ssh-keys) instead of whichever scope's region-scoped provider ran first. This keeps the account-global key's owning provider stable when the scope set or realization order changes (otherwise Pulumi could see the provider reference move between runs and replace a resource every server depends on). Because the cache dedups creation to a single caller, this provider registers exactly once and never collides on URN.
  • Drop the region-slug/container labels from the shared key — it's env-scoped, so it carries no scope-specific label regardless of which scope creates it.
  • Assumes one Hetzner account per env (the keys' env-scoped names carry no account dimension); documented as such.

Tests

  • providers/hetznerTestEnsureSshKeysSharedAcrossInstances: two instances sharing a cache return identical key objects.
  • internal/registryTestSshKeysRegisterOncePerEnvAcrossScopes: drives two registries over one Pulumi context through the real Compute().Create() path; asserts each key registers exactly once and under the dedicated hcloud-ssh-keys provider. Both assertions were verified to fail when the fix is reverted.
  • New rule: .agents/rules/ssh-keys-register-once-across-scopes.md.

go build ./..., go test -race ./..., and golangci-lint run ./... all pass.

Merge Commit Message

fix(hetzner): register env-scoped SSH keys once across realization scopes

Share one SSH-key cache across every HetznerCompute of a run and pin the
account-global keys to a dedicated, fixed-name provider, so they register
exactly once with a stable owning provider regardless of scope set/order.
Fixes the duplicate-URN failure on the first cross-scope deploy.

https://claude.ai/code/session_017Kyd98NzojozMZ19d5UCZ2

…opes

program.Run builds one HetznerCompute per realization scope (the region-less
global slice plus each region), each with its own SSH-key dedup cache. Hetzner
SSH keys are account-global and named with no region slug
(wardnet-<env>-key-{user,deploy}), so every scope registered the same URN and
the first real cross-scope deploy failed at preview with
"Duplicate resource URN '…SshKey::wardnet-<env>-key-user'".

Share one SSHKeyCache across every HetznerCompute of a run (threaded
program.Run → BuildRegistry → NewCompute) so the keys register exactly once.
Pin them to a dedicated, fixed-name provider (hcloud-ssh-keys) instead of the
winning scope's region-scoped provider, so the account-global key's owning
provider stays stable when the scope set or realization order changes. Drop the
region-slug/container labels from the shared key (env-scoped → no scope-specific
label). Assumes one Hetzner account per env.

Add regression tests at the compute and registry layers (dedup + dedicated
provider) and a rule documenting the invariant.

Claude-Session: https://claude.ai/code/session_017Kyd98NzojozMZ19d5UCZ2
@pedromvgomes pedromvgomes merged commit 64c51a8 into main Jun 28, 2026
2 checks passed
@pedromvgomes pedromvgomes deleted the fix/hetzner-ssh-keys-cross-scope branch June 28, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant